package edu.wpi.cs509.clientlogic;

import java.util.List;
import java.util.ArrayList; 

import edu.wpi.cs509.messages.client.MsgLoginReply;
import edu.wpi.cs509.messages.client.MsgLoginReply.LoginResult;
import edu.wpi.cs509.representation.ActionBaseClass;
import edu.wpi.cs509.representation.ClientGame;
import edu.wpi.cs509.representation.GameListItem;
import edu.wpi.cs509.representation.Location;
import edu.wpi.cs509.representation.PlayerPiece;
import edu.wpi.cs509.representation.StrategoPiece;
import edu.wpi.cs509.representation.TeamColor;
import edu.wpi.cs509.representation.UserStatistics;
import edu.wpi.cs509.ui.intfc.GameEventNotify;
import edu.wpi.cs509.ui.intfc.PlayerStatsInterface;
import edu.wpi.cs509.ui.intfc.RulesInterface;
import edu.wpi.cs509.ui.intfc.ClientLogicNotify;
import edu.wpi.cs509.clientlogic.ClientUpstreamInterface;
import edu.wpi.cs509.clientlogic.PlayerStatsInterfaceObject;

/**
 * This class will take messages and notifications received from
 * the server and pass them up to the GUI for handling.
 * 
 * 	@author jadelorie
 * 	@author lwante
 * 	@author mjsanchez
 */
public class ClientUpstreamLogic implements ClientUpstreamInterface{
	

	//Constructor is private 
	private ClientUpstreamLogic() {}
	
	private static class ClientUpstreamLogicSingleton {
		public static final ClientUpstreamLogic instance = new ClientUpstreamLogic();
	}
	
	public static ClientUpstreamLogic getInstance() {
		return ClientUpstreamLogicSingleton.instance;
	}
	
	/**
	 * If the result is logged-in, give the UI an ack. If the result is
	 * !logged-in, give the UI a reject.
	 */
	@Override
	public void receiveLogin(MsgLoginReply.LoginResult loginResult) {
		
		if (loginResult == LoginResult.LoggedIn) {
			//Notify the client of the successful login attempt
			ClientLogic.getInstance().getSessionEventHandle().notifyLoginSuccess(ClientLogic.getInstance().getUserSession());
		} else {
			//We have failed login - destroy the saved user session
			ClientLogic.getInstance().setUserSession(null);
			
			//Notify the client of the failed login attempt.
			ClientLogic.getInstance().getSessionEventHandle().notifyFailure(
					ClientLogicNotify.FailureReason.InvalidCredentials);
		}
	}

	@Override
	public void receiveLogout() {
		// TODO: Make sure we destroy the user session.
		
	}

	@Override
	public void receiveLeaderBoard(List<UserStatistics> leaderBoardStats, int start_rank) {
		//Make sure we got stats back
		if (leaderBoardStats == null) {
			//TODO: What do do if we are null?  Probably need a new failure reason
			//of "unable to find statistics" or something similar.
			return;
		}
		
		//Create a new array list.  We will build a list of PlayerStatsInterfaceObjects
		//from the list of userStatistics objects.
		ArrayList<PlayerStatsInterface> statsList = new ArrayList<PlayerStatsInterface>();
		for (UserStatistics userStats : leaderBoardStats) {
			statsList.add(new PlayerStatsInterfaceObject(userStats, ++start_rank));
		}
		
		//Notify the client logic of the received statistics
		//TODO: Need to change the second param called (start rank of list) to the real rank
		ClientLogic.getInstance().getSessionEventHandle().notifyLeaderBoard(
				statsList, start_rank, statsList.size());
		
	}

	/**
	 * @param playerStats - the UserStatistics object returned from the server
	 * @param rank - the rank of the player in the stats
	 */
	public void receivePersonalStats(UserStatistics playerStats, int rank) {
		//Make sure we got stats back
		if (playerStats == null) {
			//TODO: What do do if we are null?  Probably need a new failure reason
			//of "unable to find statistics" or something similar.
			System.out.println("User statistics returned null...");
			return;
		}
		
		//Notify the UI of the stats via the sessionEventHandle
		ClientLogic.getInstance().getSessionEventHandle().notifyPlayerStats(
				new PlayerStatsInterfaceObject(playerStats, ++rank));
		
	}

	/**
	 * Receives the servers response to the Joinable Games Message.
	 * @param joinableGames - List of games that are joinable.
	 */
	public void receiveGames(List<GameListItem> games) {
		ClientLogic.getInstance().getSessionEventHandle().notifyGames(games);
	}

	@Override
	public void receiveStrategoRules(RulesInterface rules) {
		ClientLogic.getInstance().getSessionEventHandle().notifyRulesPage(rules);
	}

	/**
	 * This handles replies to move-pc messages where an attack has occured.
	 * @param success  - did the move succeed?
	 * @param game - client game for the attack
	 * @param session - session for the attack
	 * @param attackerGrid - 'from', where piece started
	 * @param defenderGrid - 'to', where piece was moved to
	 * @param nextTurn - who's turn it is
	 */
	public void receiveAttack(boolean success, ClientGame game, UserSession session, ActionBaseClass attack, TeamColor nextTurn) {
		Location attackerGrid = attack.getPieceBeingMovedOriginalSourceLocation();
		Location defenderGrid = attack.getPieceBeingMovedOriginalDestinationLocation();
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		//PlayerPiece attacker = ClientLogic.getInstance().getGame().getGameboard().getPlayerPieceFromLocation(attackerGrid);
		PlayerPiece attacker = attack.getUiAttacker();
		//PlayerPiece defender = ClientLogic.getInstance().getGame().getGameboard().getPlayerPieceFromLocation(defenderGrid);
		PlayerPiece defender = attack.getUiDefender();
		ClientLogicNotify sessionHandle = ClientLogic.getInstance().getSessionEventHandle();
		System.out.println("Client received an attack. Attacker: "+attacker.getColor()+" Defender: "+defender.getColor());
		if (sessionHandle == null) {
			System.out.println("ERROR: ClientLogicNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (!success) {
			//TODO: Pass appropriate error message
			sessionHandle.notifyFailure(ClientLogicNotify.FailureReason.ServerLimitReached);
		}
		
		if (game == null) {
			//TODO: Pass appropriate error message
			sessionHandle.notifyFailure(ClientLogicNotify.FailureReason.ServerLimitReached);
		}
		
		ClientLogic.getInstance().setGame(game);

		// Opponent piece obfuscation
		/*
		PlayerPiece obDefender = new PlayerPiece( StrategoPiece.HIDDEN, defender.getColor(), defender.getLocation() );
		
		if (game.obfuscatePieces() && (game.getMyColor() == attacker.getColor())) {
			// The attacker does not get to see the defender piece, but can see their own piece
			gameHandle.notifyAttack(attacker, attackerGrid, obDefender, defenderGrid );
		} else {
			// The defender get to see both the attacker and defender pieces 
			gameHandle.notifyAttack(attacker, attackerGrid, defender, defenderGrid );
		}
		*/
		gameHandle.notifyAttack(attacker, attackerGrid, defender, defenderGrid );
		
		//See if its now your turn.  If so, notify the UI.
		if (nextTurn == game.getMyColor()) {
			gameHandle.notifyItsYourTurn();
		}
		
		//tell the UI to refresh the game board
		gameHandle.notifyRefreshGameBoard();	
		
		// Notify if now my turn
		if (game.isMyTurn()) {
			gameHandle.notifyItsYourTurn();
		}
	}

	@Override
	public void receiveGameOver(ClientGame game, boolean winner) {
		
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
				
		//tell the UI whether the player wins
		gameHandle.notifyGameOver(winner);
	}

	@Override
	public void receiveGameStarted( ClientGame game ) {
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		//tell the UI to refresh the game board
		gameHandle.notifyGameStarted();	
		
		// inform if it is my turn; red goes first
		if (game.isMyTurn()) {
			gameHandle.notifyItsYourTurn();
		}
	}

	@Override
	public void receiveItsYourTurn( ClientGame game ) {

		if (game.isMyTurn()) {
			
			GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
			if (gameHandle == null) {
				System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
				return;
			}
				
			//tell the UI it's their players turn
			gameHandle.notifyItsYourTurn();
		} else {
			System.out.println("ERROR: It's my turn notification, but game state says it's not my turn");
		}
	}

	//TODO: Do we really need to be passed the UserSession?
	@Override
	public void receiveOpponentQuit(UserSession session) {
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (session == null) {
			System.out.println("ERROR: UserSession session is null");
			return;
		}
	
		gameHandle.notifyOpponentQuit();
	}

		
	/**
	 * This handle non-attack pc-move-reply messages.
	 */
	public void receiveNonAttack(boolean success, ClientGame game, PlayerPiece piece, UserSession session, Location from,
				Location to, TeamColor nextTurn) {
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		PlayerPiece pieceMoved = piece;
		ClientLogicNotify sessionHandle = ClientLogic.getInstance().getSessionEventHandle();
		System.out.println("Client received piece movement.");
		if (sessionHandle == null) {
			System.out.println("ERROR: ClientLogicNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (!success) {
			//TODO: Pass appropriate error message
			sessionHandle.notifyFailure(ClientLogicNotify.FailureReason.ServerLimitReached);
			return;
		}
		
		if (game == null) {
			//TODO: Pass appropriate error message
			sessionHandle.notifyFailure(ClientLogicNotify.FailureReason.ServerLimitReached);
			return;
		}
		
		ClientLogic.getInstance().setGame(game);
		
		if ((from == null) || (from.isInBoneyard())) {    
			System.out.println("TOP");
			//Opponent piece obfuscation
			if (game.obfuscatePieces() && (piece.getColor() != game.getMyColor())) {
				piece.obfuscate();
			}
			gameHandle.notifyPiecePlaced(pieceMoved, to);
		} else if ((to == null) || (to.isInBoneyard())) {
			System.out.println("MIDDLE");
			gameHandle.notifyPieceRemoved(pieceMoved, from);
		} else {
			System.out.println("END");
			if (game.obfuscatePieces() && (piece.getColor() != game.getMyColor())) {
				piece.obfuscate();
			}

			gameHandle.notifyPieceMoved(pieceMoved, from, to);
			
			// Notify if now my turn
			if (game.isMyTurn()) {
				gameHandle.notifyItsYourTurn();
			}
		}
		
		System.out.println("Current turn: " + nextTurn);
		//See if its now your turn.  If so, notify the UI.
		//if (nextTurn == game.getMyColor()) {
		//	gameHandle.notifyItsYourTurn();
		//}
		
		//tell the UI to refresh the game board
		gameHandle.notifyRefreshGameBoard();	
	}
	
	@Override
	public void receivePieceMoved(ActionBaseClass lastMove, boolean success, ClientGame game, 
			UserSession session, TeamColor nextTurn) {
		
		//is this an attack?
		if (lastMove.isAnAttack()) {
			
			 receiveAttack(success, game, session, 
					 lastMove, 
					 nextTurn);
		} else {
			receiveNonAttack(success, game, lastMove.getPieceBeingMoved(), session, 
					lastMove.getPieceBeingMovedOriginalSourceLocation(), lastMove.getPieceBeingMovedOriginalDestinationLocation(), 
					nextTurn);
		}
	}
	
	@Override
	public void receiveRefreshGameBoard() {
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		//tell the UI to refresh the game board
		gameHandle.notifyRefreshGameBoard();				
	}

	@Override
	public void receiveServerConnectionLost() {
		ClientLogicNotify sessionHandle = ClientLogic.getInstance().getSessionEventHandle();
		
		if (sessionHandle == null) {
			System.out.println("ERROR: ClientLogicNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		sessionHandle.ServerConnectionLost();
	}

	@Override
	public void receiveStartNewGame(ClientGame game, boolean success, String errorMessage) {
		ClientLogicNotify sessionHandle = ClientLogic.getInstance().getSessionEventHandle();
		if (sessionHandle == null) {
			System.out.println("ERROR: ClientLogicNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		if (gameHandle == null) {
			System.out.println("ERROR: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}
		
		if (game == null || !success) {
			sessionHandle.notifyFailure(ClientLogicNotify.FailureReason.ServerLimitReached);
		}
		//TODO: Change Game -> GameInterface for UI
		GameInterfaceObject gameIntf = new GameInterfaceObject(game);
		
		//Pass the gameInterface object up to the client.
		if (!game.isObserverGame()) {
			sessionHandle.notifyGameJoined(gameIntf);	
		} else {
			sessionHandle.notifyGameObserved(gameIntf);	
		}
		gameHandle.notifyRefreshGameBoard();				

	}

	@Override
	public void receiveOpponentJoined(ClientGame game) {
				
		System.out.println("ClientLogic: Opponent has joined the game");
		
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		if (gameHandle == null) {
			System.out.println("ERROR: receiveOpponentJoined: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}

		if (game == null) {
			System.out.println("ERROR: receiveOpponentJoined: game argument is null...this should not happen - Debug this");
			return;
		}
		
		ClientLogic.getInstance().setGame(game);
		
		gameHandle.notifyOpponentJoined();

	}

	@Override
	public void receiveOpponentReady(ClientGame game) {
		System.out.println("ClientLogic: Opponent is ready!");
		
		GameEventNotify gameHandle = ClientLogic.getInstance().getGameEventHandle();
		if (gameHandle == null) {
			System.out.println("ERROR: receiveOpponentReady: GameEventNotify handle is null...this should not happen - Debug this");
			return;
		}

		if (game == null) {
			System.out.println("ERROR: receiveOpponentReady: game argument is null...this should not happen - Debug this");
			return;
		}
		
		ClientLogic.getInstance().setGame(game);
		
		gameHandle.notifyOpponentReady();
	}
	
}
